//==============================================================================
//
//                  XMLParser - the XML parser class
//
//               Copyright (C) 2018  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================

#include <string>
#include <cstring>

#include "XMLParser.h"


namespace gpxtools
{
  
  XMLParser::XMLParser(XMLParserHandler *handler) :
    _handler(handler),
    _parser(0),
    _path(""),
    _errorText(),
    _errorLineNumber(0),
    _errorColumnNumber(0)
  {
  }

  XMLParser::~XMLParser()
  {
    if (_parser != 0)
    {
      stopExpat();
    }
  }

  // Parsing

  bool XMLParser::parse(const char *data, int length, bool isFinal)
  {
    bool ok = false;

    if (_parser == 0)
    {
      startExpat();

      if (_parser == 0)
      {
        return false;
      }

      _path = "";
      _errorText = "";
      _errorLineNumber = 0;
      _errorColumnNumber = 0;
    }

    ok = (XML_Parse(_parser, data, length, isFinal) != XML_STATUS_ERROR);

    if (!ok)
    {
      _errorText         = XML_ErrorString(XML_GetErrorCode(_parser));
      _errorLineNumber   = XML_GetCurrentLineNumber(_parser);
      _errorColumnNumber = XML_GetCurrentColumnNumber(_parser);
    }

    if (isFinal)
    {
      stopExpat();
    }

    return ok;
  }

  bool XMLParser::parse(const char *text, bool isFinal)
  {
    return parse(text, strlen(text), isFinal);
  }

  bool XMLParser::parse(const std::string &data, bool isFinal)
  {
    return parse(data.c_str(), data.length(), isFinal);
  }

  // Parse a stream

  bool XMLParser::parse(std::istream &stream)
  {
    bool ok = stream.good();

    if (ok)
    {
      char buffer[4096];

      while ((ok) && (stream.good()))
      {
        stream.read(buffer, sizeof(buffer));

        ok = parse(buffer, (int) stream.gcount(), (stream.gcount() < sizeof(buffer)));
      }
    }

    return ok;
  }

  // Helpers
  void XMLParser::trim(std::string &text)
  {
    std::size_t start = text.find_first_not_of(" \t\r\n");

    if (start == std::string::npos)
    {
      text.clear();
    }
    else
    {
      text.erase(0, start);

      std::size_t end   = text.find_last_not_of(" \t\r\n");

      text.erase(end + 1);
    }
  }

  // Privates

  void XMLParser::startExpat()
  {
    _parser = XML_ParserCreate(NULL);
    
    XML_SetUserData(_parser, this);
  
    XML_SetElementHandler(_parser, startElementHandler, endElementHandler);
    XML_SetCharacterDataHandler(_parser, characterDataHandler);
  }
  
  void XMLParser::stopExpat()
  {
    XML_ParserFree(_parser);
    
    _parser = 0;
  }

  void XMLParser::startElementHandler(void *userData, const XML_Char *name, const XML_Char **atts)
  {
    XMLParser *self = static_cast<XMLParser*>(userData);
    
    XMLParserHandler::Attributes attributes;

    for (int i = 0; atts[i] != NULL; i+=2)
    {
      attributes[std::string(atts[i])] = atts[i+1];
    }

    self->_path.append("/");
    self->_path.append(name);

    self->_handler->startElement(self->_path, std::string(name), attributes);
  }

  void XMLParser::endElementHandler(void *userData, const XML_Char *name)
  {
    XMLParser *self = static_cast<XMLParser*>(userData);
    
    self->_handler->endElement(self->_path, std::string(name));

    size_t i = self->_path.find_last_of('/');
    if (i != std::string::npos)
    {
      self->_path.erase(i);
    }
  }

  void XMLParser::characterDataHandler(void *userData, const XML_Char *s, int len)
  {
    XMLParser *self = static_cast<XMLParser*>(userData);

    self->_handler->text(self->_path, std::string(s, len));
  }
}
